/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package solitaireofdoom.freecell;

import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.io.*;

// XML imports
import java.io.File;
import org.w3c.dom.*;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

import solitaireofdoom.solitaire.*;

/**
 * The Freecell class is like the Klondike class in that they both display the piles and cards necessary for playing the respective solitaires. Freecell also inherits JComponent and serves the same functions as the Klondike class.
 *
 * @author Administrator
 */
public class Freecell extends SolitaireType
{
        // calls on objects
    private BufferedImage cards;
    private SolitaireFrame frame;

    private FreecellCellPile[] cellPiles;
    private FreecellFoundationPile[] foundationPiles;
    private FreecellTableuPile[] tableuPiles;

    private final boolean checkPlausibleOutput = false; // Whether the checkPlausibleMoves should show debug information or not
    public final String name = "Freecell";

    public Freecell(SolitaireFrame frame, BufferedImage cards)
    {
        this.cards = cards;
        this.frame = frame;

        this.setSize(735, 710);

        int playingPileLeftMargin = 12;

        this.cellPiles = new FreecellCellPile[4];
        for (int i = 0; i < this.cellPiles.length; i++)
        {
            this.cellPiles[i] = new FreecellCellPile(this, cards);
            this.cellPiles[i].setSize(Card.CardWidth, Card.CardHeight);
            this.cellPiles[i].setLocation(playingPileLeftMargin + i * (Card.CardWidth + 1) + i * 10, 0);
            this.add(this.cellPiles[i]);
        }

        this.foundationPiles = new FreecellFoundationPile[4];
        for (int i = 4; i < this.foundationPiles.length + 4; i++)
        {
            this.foundationPiles[i - 4] = new FreecellFoundationPile(Card.Suits[i - 4], this, cards);
            this.foundationPiles[i - 4].setSize(Card.CardWidth, Card.CardHeight);
            this.foundationPiles[i - 4].setLocation(playingPileLeftMargin + i * (Card.CardWidth + 1) + i * 10, 0);
            this.add(this.foundationPiles[i - 4]);
        }

        this.tableuPiles = new FreecellTableuPile[8];
        for (int i = 0; i < this.tableuPiles.length; i++)
        {
            this.tableuPiles[i] = new FreecellTableuPile(this, cards);
            this.tableuPiles[i].setSize(81, 605);
            this.tableuPiles[i].setLocation(playingPileLeftMargin + i * (Card.CardWidth + 1) + i * 10, Card.CardHeight + 10);
            this.add(this.tableuPiles[i]);
        }

        KlondikeMouseHandler mouseHandler = new KlondikeMouseHandler();
        this.addMouseListener(mouseHandler);

        newGame();
    }

    /**
     * Starts a new freecell game
     */
    public void newGame()
    {
        gameTimer.stop();

        for (int i = 0; i < this.cellPiles.length; i++)
            this.cellPiles[i].clear();
        for (int i = 0; i < this.foundationPiles.length; i++)
            this.foundationPiles[i].clear();
        for (int i = 0; i < this.tableuPiles.length; i++)
            this.tableuPiles[i].clear();

        Deck d = new Deck();
        d.clearPile();
        d.fillPile();
        d.shuffle();

        for (int i = 0; i < this.tableuPiles.length; i++)
        {
            if (i == this.tableuPiles.length - 1)
            {
                for (int j = 0; j < d.size(); j++)
                {
                    CardCanvas cc = new CardCanvas(d.getCard(j), cards);
                    cc.getCard().setFacing(true);
                    cc.pile = this.tableuPiles[i];
                    this.tableuPiles[i].pileCards.add(cc);
                }

                this.tableuPiles[i].redraw();
            }
            else
            {

                Deck tableuDeck = d.getCards((i < 4 ? 7 : 6), 1);

                for (int j = 0; j < tableuDeck.size(); j++)
                {
                    CardCanvas cc = new CardCanvas(tableuDeck.getCard(j), cards);
                    cc.pile = this.tableuPiles[i];
                    this.tableuPiles[i].pileCards.add(cc);
                }

                this.tableuPiles[i].redraw();
            }
        }
    }

    /**
     * Method moves the cards that can be moved to the suit pile
     */
    public void autoMoveToSuit()
    {
        int moves = 0;

        for (int i = 0; i < this.tableuPiles.length; i++)
        {
            if (this.tableuPiles[i].pileCards.size() > 0)
            {
                Card c = this.tableuPiles[i].pileCards.lastElement().getCard();

                for (int j = 0; j < this.foundationPiles.length; j++)
                {
                    Card cAbove = this.foundationPiles[j].pileCards.lastElement().getCard();

                    if (cAbove.getSuit() == c.getSuit() &&
                        (cAbove.getRank() + 1 == c.getRank() ||
                        (cAbove.getRank() == 999 && c.getRank() == 1)))
                    {
                        this.foundationPiles[j].add(this.tableuPiles[i].pileCards.lastElement());
                        this.tableuPiles[i].remove(this.tableuPiles[i].pileCards.lastElement());

                        moves++;
                    }
                }
            }
        }

        for (int i = 0; i < this.cellPiles.length; i++)
        {
            if (this.cellPiles[i].pileCards.size() == 2)
            {
                Card c = this.cellPiles[i].pileCards.lastElement().getCard();

                for (int j = 0; j < this.foundationPiles.length; j++)
                {
                    Card cAbove = this.foundationPiles[i].pileCards.lastElement().getCard();

                    if (cAbove.getSuit() == c.getSuit() &&
                        (cAbove.getRank() + 1 == c.getRank() ||
                        (cAbove.getRank() == 999 && c.getRank() == 1)))
                    {
                        this.foundationPiles[j].add(this.cellPiles[i].pileCards.lastElement());
                        this.cellPiles[i].remove(this.cellPiles[i].pileCards.lastElement());

                        moves++;
                    }
                }
            }
        }

        if (moves > 0)
            autoMoveToSuit();
        else
        {
            pileChanged(false);
            return;
        }
    }

    /**
     * Reports that a pile was changed
     *
     * @param drawPileFlip was the draw pile flipped during this pile change?
     */
    public void pileChanged(boolean drawPileFlip)
    {
        if (!gameTimer.isRunning())
        {
            this.gameTimeSeconds = 0;
            this.gameTimer.start();
        }

        for (int i = 0; i < this.cellPiles.length; i++)
            for (int j = 0; j < this.cellPiles[i].pileCards.size(); j++)
                this.cellPiles[i].pileCards.elementAt(j).hideHint();

        for (int i = 0; i < this.tableuPiles.length; i++)
            for (int j = 0; j < this.tableuPiles[i].pileCards.size(); j++)
                this.tableuPiles[i].pileCards.elementAt(j).hideHint();

        for (int i = 0; i < this.foundationPiles.length; i++)
            for (int j = 0; j < this.foundationPiles[i].pileCards.size(); j++)
                this.foundationPiles[i].pileCards.elementAt(j).hideHint();

        this.checkPlausibleMoves(false);
        this.checkFinished();
    }

    /**
     * Check if the game has finished
     */
    public void checkFinished()
    {
        boolean isFinished = true;

        for (int i = 0; i < this.foundationPiles.length; i++)
            isFinished = isFinished && this.foundationPiles[i].pileCards.size() == 14;

        if (isFinished)
        {
            this.gameTimer.stop();

            frame.gameFinished(this, this.gameTimeSeconds);
        }
    }

    /**
     * Checks how many moves are possible
     */
    public void checkPlausibleMoves(boolean showHint)
    {
        if (showHint)
        {
            for (int i = 0; i < this.tableuPiles.length; i++)
            {
                if (this.tableuPiles[i].pileCards.size() > 0)
                {
                    Card c1 = this.tableuPiles[i].pileCards.lastElement().getCard();
                    Card c2 = null;
                    if (this.tableuPiles[i].pileCards.size() > 1)
                        c2 = this.tableuPiles[i].pileCards.elementAt(this.tableuPiles[i].pileCards.size() - 2).getCard();

                    for (int j = 0; j < this.tableuPiles.length; j++)
                    {
                        if (i != j && this.tableuPiles[j].pileCards.size() > 0)
                        {
                            Card cAbove = this.tableuPiles[j].pileCards.lastElement().getCard();

                            if (c1.getRank() + 1 == cAbove.getRank() &&
                                    (((cAbove.getSuit() == 'H' || cAbove.getSuit() == 'D') &&
                                    (c1.getSuit() == 'S' || c1.getSuit() == 'C')) ||
                                    ((cAbove.getSuit() == 'S' || cAbove.getSuit() == 'C') &&
                                    (c1.getSuit() == 'H' || c1.getSuit() == 'D'))))
                            {
                                this.tableuPiles[i].pileCards.lastElement().showHint();
                            }
                            else if (c2 != null)
                            {
                                if (c2.getRank() + 1 == cAbove.getRank() &&
                                        (((cAbove.getSuit() == 'H' || cAbove.getSuit() == 'D') &&
                                        (c2.getSuit() == 'S' || c2.getSuit() == 'C')) ||
                                        ((cAbove.getSuit() == 'S' || cAbove.getSuit() == 'C') &&
                                        (c2.getSuit() == 'H' || c2.getSuit() == 'D'))))
                                {
                                    this.tableuPiles[i].pileCards.lastElement().showHint();
                                    this.tableuPiles[i].pileCards.elementAt(this.tableuPiles[i].pileCards.size() - 2).showHint();
                                }
                            }
                        }
                    }

                    for (int j = 0; j < this.foundationPiles.length; j++)
                    {
                        if (this.foundationPiles[j].pileCards.size() > 1)
                        {
                            Card cAbove = this.foundationPiles[j].pileCards.lastElement().getCard();

                            if (c1.getSuit() == cAbove.getSuit() && (c1.getRank() == cAbove.getRank() + 1 || (c1.getRank() == 1 && cAbove.getRank() == Card.DummyAceCards)))
                            {
                                this.tableuPiles[i].pileCards.lastElement().showHint();
                            }
                            else if (c2 != null)
                            {
                                if (c2.getSuit() == cAbove.getSuit() && (c2.getRank() == cAbove.getRank() + 1 || (c2.getRank() == 1 && cAbove.getRank() == Card.DummyAceCards)))
                                {
                                    this.tableuPiles[i].pileCards.lastElement().showHint();
                                    this.tableuPiles[i].pileCards.elementAt(this.tableuPiles[i].pileCards.size() - 2).showHint();
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Shows a plausible move to the user
     */
    public void showHint()
    {
        checkPlausibleMoves(true);
    }

    /**
     * Loads a saved freecell game
     */
    public void loadGame()
    {
        for (int i = 0; i < this.cellPiles.length; i++)
            this.cellPiles[i].pileCards.clear();
        for (int i = 0; i < this.foundationPiles.length; i++)
            this.foundationPiles[i].pileCards.clear();
        for (int i = 0; i < this.tableuPiles.length; i++)
            this.tableuPiles[i].pileCards.clear();

        File f = new File(System.getProperty("user.dir") + "\\Freecellsave.xml");

        if (f.exists())
        {
            try
            {
                DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
                Document doc = docBuilder.parse(f);

                doc.getDocumentElement().normalize();

                NodeList cellPiles = doc.getElementsByTagName("cellPile");
                for (int p = 0; p < cellPiles.getLength(); p++)
                {
                    NodeList cellPileCards = cellPiles.item(p).getChildNodes();
                    for (int i = 0; i < cellPileCards.getLength(); i++)
                    {
                        Node card = cellPileCards.item(i);
                        if (card.getNodeName().compareTo("card") == 0)
                        {
                            NamedNodeMap attributes = card.getAttributes();
                            if (attributes.getLength() > 0)
                            {
                                CardCanvas cc = new CardCanvas(
                                    new Card(attributes.getNamedItem("suit").getTextContent().charAt(0),
                                        Integer.parseInt(attributes.getNamedItem("rank").getTextContent()),
                                        Boolean.parseBoolean(attributes.getNamedItem("facing").getTextContent())),
                                    cards
                                );

                                this.cellPiles[Integer.parseInt(cellPiles.item(p).getAttributes().getNamedItem("column").getTextContent()) - 1].add(cc);
                            }
                        }
                    }
                }

                NodeList foundationPiles = doc.getElementsByTagName("foundationPile");
                for (int p = 0; p < foundationPiles.getLength(); p++)
                {
                    NodeList foundationPileCards = foundationPiles.item(p).getChildNodes();
                    for (int i = 0; i < foundationPileCards.getLength(); i++)
                    {
                        Node card = foundationPileCards.item(i);
                        if (card.getNodeName().compareTo("card") == 0)
                        {
                            NamedNodeMap attributes = card.getAttributes();
                            if (attributes.getLength() > 0)
                            {
                                CardCanvas cc = new CardCanvas(
                                    new Card(attributes.getNamedItem("suit").getTextContent().charAt(0),
                                        Integer.parseInt(attributes.getNamedItem("rank").getTextContent()),
                                        Boolean.parseBoolean(attributes.getNamedItem("facing").getTextContent())),
                                    cards
                                );

                                char suit = foundationPiles.item(p).getAttributes().getNamedItem("suit").getTextContent().charAt(0);
                                this.foundationPiles[(suit == 'H' ? 0 : (suit == 'S' ? 1 : (suit == 'D' ? 2 : suit == 'C' ? 3 : -1)))].add(cc);
                            }
                        }
                    }
                }

                NodeList tableuPiles = doc.getElementsByTagName("tableuPile");
                for (int p = 0; p < tableuPiles.getLength(); p++)
                {
                    NodeList tableuPileCards = tableuPiles.item(p).getChildNodes();
                    for (int i = 0; i < tableuPileCards.getLength(); i++)
                    {
                        Node card = tableuPileCards.item(i);
                        if (card.getNodeName().compareTo("card") == 0)
                        {
                            NamedNodeMap attributes = card.getAttributes();
                            if (attributes.getLength() > 0)
                            {
                                CardCanvas cc = new CardCanvas(
                                    new Card(attributes.getNamedItem("suit").getTextContent().charAt(0),
                                        Integer.parseInt(attributes.getNamedItem("rank").getTextContent()),
                                        Boolean.parseBoolean(attributes.getNamedItem("facing").getTextContent())),
                                    cards
                                );

                                this.tableuPiles[Integer.parseInt(tableuPiles.item(p).getAttributes().getNamedItem("column").getTextContent()) - 1].add(cc);
                            }
                        }
                    }
                }
            }
            catch(Exception e)
            {

            }
        }
        else
        {
            this.newGame();
        }

        for (int i = 0; i < this.cellPiles.length; i++)
            this.cellPiles[i].redraw();
        for (int i = 0; i < this.foundationPiles.length; i++)
            this.foundationPiles[i].redraw();
        for (int i = 0; i < this.tableuPiles.length; i++)
            this.tableuPiles[i].redraw();
    }

    /**
     * Save the current game
     */
    public void saveGame()
    {
        File f = new File(System.getProperty("user.dir") + "\\Freecellsave.xml");

        try
        {
            if (!f.exists())
                f.createNewFile();

            BufferedWriter writer = new BufferedWriter(new FileWriter(f));

            writer.write("<solitaire type=\"Freecell\">" + "\n");

            for (int p = 0; p < this.cellPiles.length; p++)
            {
                writer.write("<cellPile column=\"" + (p + 1) + "\">" + "\n");
                for (int i = 0; i < this.cellPiles[p].pileCards.size(); i++)
                {
                    Card c = this.cellPiles[p].pileCards.elementAt(i).getCard();

                    writer.write("\t\t<card suit=\"" + c.getSuit() + "\" rank=\"" + c.getRank() + "\" facing=\"" + c.getFacing() + "\" />"  + "\n");
                }
                writer.write("</cellPile>" + "\n");
            }

            for (int p = 0; p < this.foundationPiles.length; p++)
            {
                writer.write("<foundationPile suit=\"" + (p == 0 ? 'H' : (p == 1 ? 'S' : (p == 2 ? 'D' : (p == 3 ? 'C' : '\0')))) + "\">" + "\n");
                for (int i = 0; i < this.foundationPiles[p].pileCards.size(); i++)
                {
                    Card c = this.foundationPiles[p].pileCards.elementAt(i).getCard();

                    writer.write("\t\t<card suit=\"" + c.getSuit() + "\" rank=\"" + c.getRank() + "\" facing=\"" + c.getFacing() + "\" />"  + "\n");
                }
                writer.write("</foundationPile>" + "\n");
            }

            for (int p = 0; p < this.tableuPiles.length; p++)
            {
                writer.write("<tableuPile column=\"" + (p + 1) + "\">" + "\n");
                for (int i = 0; i < this.tableuPiles[p].pileCards.size(); i++)
                {
                    Card c = this.tableuPiles[p].pileCards.elementAt(i).getCard();

                    writer.write("\t\t<card suit=\"" + c.getSuit() + "\" rank=\"" + c.getRank() + "\" facing=\"" + c.getFacing() + "\" />"  + "\n");
                }
                writer.write("</tableuPile>" + "\n");
            }

            writer.write("</solitaire>" + "\n");

            writer.flush();
            writer.close();
        }
        catch (Exception exc)
        {
            
        }
    }

    /**
     * class to handle the mouse options
     */
    class KlondikeMouseHandler extends MouseAdapter
    {
        /**
         * Checks for mouse click and then performs an action based on the facing of the card.
         *
         * @param e <i>MouseEvent</i>
         */
        public void mouseClicked(MouseEvent e)
        {
            if (e.getButton() == e.BUTTON3)
            {
                autoMoveToSuit(); // this methos is in
            }
        }
    }
}
